package com.fanketly.musicdiary.ui.components

import android.util.Log
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.*
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.Path.Companion.combine
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.fanketly.musicdiary.data.TAG
import kotlin.math.sqrt


@Composable
fun MyBottomAppBar(
    modifier: Modifier = Modifier,
    cutoutShape: Shape,
    cutoutShapeTop: Boolean = true,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    elevation: Dp = AppBarDefaults.BottomAppBarElevation,
    contentPadding: PaddingValues = AppBarDefaults.ContentPadding,
    content: @Composable RowScope.() -> Unit
) {
    val density = LocalDensity.current
    val view = LocalView.current
    val shape = BottomAppBarCutoutShape(
        cutoutShape,
        cutoutShapeTop,
        with(density) {
//            Log.i(TAG, "MyBottomAppBar: ${view.width - 62.dp.toPx()}")
            //46+padding
            view.width - 62.dp.toPx()
//            894f
        }
    )

    Surface(
        modifier = modifier,
        color = backgroundColor,
        contentColor = contentColor,
        elevation = elevation,
        shape = shape,
    ) {
        CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
            Row(
                Modifier
                    .fillMaxWidth()
                    .padding(contentPadding)
                    .height(AppBarHeight),
                horizontalArrangement = Arrangement.Start,
                verticalAlignment = Alignment.CenterVertically,
                content = content
            )
        }
    }
}


/**
 *一个 [Shape]，表示带有切口的底部应用栏。绘制的切口将 [cutoutShape] 在所有边上增加 [BottomAppBarCutoutOffset] 的大小。
 */
private data class BottomAppBarCutoutShape(
    val cutoutShape: Shape,
    val cutoutShapeTop: Boolean,
    val paddingStart: Float
//    val fabPlacement: FabPlacement
) : Shape {

    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        val boundingRectangle = Path().apply {
            addRect(Rect(0f, 0f, size.width, size.height))
        }
        val path2 = Path().apply {
            //FAB 和切口之间的所有侧面的间隙
            val cutoutOffset = with(density) { BottomAppBarCutoutOffset.toPx() }
            val fabHeight = with(density) { 46.dp.toPx() }
            val layoutHeight = with(density) { 56.dp.toPx() }
            val cutoutSize = Size(
//            width = fabPlacement.width + (cutoutOffset * 2),
//            height = fabPlacement.height + (cutoutOffset * 2)
                width = fabHeight + (cutoutOffset * 2),
                height = fabHeight + (cutoutOffset * 2)
            )
            val cutoutStartX = paddingStart - cutoutOffset
            val cutoutEndX = cutoutStartX + cutoutSize.width
            val cutoutRadius = cutoutSize.height / 2f

            if (cutoutShape == CircleShape) {
                val edgeRadius = with(density) { BottomAppBarRoundedEdgeRadius.toPx() }
                // TODO: possibly support providing a custom vertical offset?
                addRoundedEdges(
                    cutoutShapeTop,
                    cutoutStartX,
                    cutoutEndX,
                    cutoutRadius,
                    edgeRadius,
                    0f,
                    layoutHeight
                )
            }

        }
        val path = Path().apply {
            addCutoutShape(cutoutShapeTop, layoutDirection, density)
//             从边界矩形中减去这条路径
            op(this, path2, PathOperation.Union)
            op(boundingRectangle, this, PathOperation.Difference)

        }

        return Outline.Generic(path)
    }

    /**
     * 将填充的 [cut out Shape] 添加到 [Path]。该路径可以从用于应用栏的主矩形路径中减去，以创建最终的剪切形状
     * Adds the filled [cutoutShape] to the [Path]. The path can the be subtracted from the main
     * rectangle path used for the app bar, to create the resulting cutout shape.
     */
    private fun Path.addCutoutShape(
        cutoutShapeTop: Boolean,
        layoutDirection: LayoutDirection,
        density: Density
    ) {
        //FAB 和切口之间的所有侧面的间隙
        val cutoutOffset = with(density) { BottomAppBarCutoutOffset.toPx() }
        val fabHeight = with(density) { 46.dp.toPx() }
//        val layoutHeight = with(density) { 56.dp.toPx() }
        val cutoutSize = Size(
//            width = fabPlacement.width + (cutoutOffset * 2),
//            height = fabPlacement.height + (cutoutOffset * 2)
            width = fabHeight + (cutoutOffset * 2),
            height = fabHeight + (cutoutOffset * 2)
        )

//        val cutoutStartX = paddingStart - cutoutOffset
        val cutoutStartX = paddingStart - cutoutOffset
        val cutoutEndX = cutoutStartX + cutoutSize.width

        val cutoutRadius = cutoutSize.height / 2f
        //将切口向上移动一半高度，因此实际上只有切口的下半部分被切入应用栏
        val cutoutStartY =
            if (cutoutShapeTop) -cutoutRadius else cutoutRadius
//       val cutoutStartY = -cutoutRadius

        if (cutoutShape == CircleShape) {
            addOutline(cutoutShape.createOutline(cutoutSize, layoutDirection, density))

            translate(Offset(cutoutStartX, cutoutStartY))
//            val edgeRadius = with(density) { BottomAppBarRoundedEdgeRadius.toPx() }
//            addRoundedEdges(cutoutStartX, cutoutEndX, cutoutRadius, edgeRadius, 0f, layoutHeight)
        }

    }

    /**
     * 将圆角边缘添加到表示 BottomAppBar 中的圆形切口的 [Path]。
     * 为左右边缘添加一条曲线，在它们之间绘制一条直线 - 这与切口形状相结合会产生可以从应用栏的边界矩形中减去的整体切口路径。
    @param cutoutStartPosition 切口的绝对起始位置
    @param cutoutEndPosition 切口的绝对结束位置
    @param cutoutRadius 切口的圆形边缘的半径 - 对于典型的圆形 FAB，这只是圆形切口的半径，但在在扩展 FAB 的情况下，我们可以将其建模为连接到矩形的两侧的两个圆。
    @param roundedEdgeRadius 应该将圆形边缘绘制到切口与应用栏相交的点的距离。
    @param verticalOffset 应用栏离切割圆中心有多远
     */
    private fun Path.addRoundedEdges(
        cutoutShapeTop: Boolean,
        cutoutStartPosition: Float,
        cutoutEndPosition: Float,
        cutoutRadius: Float,
        roundedEdgeRadius: Float,
        verticalOffset: Float,
        layoutHeight: Float,
    ) {
        // 在切口与应用栏相交的地方，如果切口与应用栏没有垂直对齐，则相交将不等于圆的半径。
        val appBarInterceptOffset = calculateCutoutCircleYIntercept(cutoutRadius, verticalOffset)
        val appBarInterceptStartX = cutoutStartPosition + (cutoutRadius + appBarInterceptOffset)
        val appBarInterceptEndX = cutoutEndPosition - (cutoutRadius + appBarInterceptOffset)

        // 控制点与剪切截距的距离。我们将其设置为尽可能小，以便我们拥有最“圆润”的曲线。
        val controlPointOffset = 1f

        // 控制点距切口半径中心的距离
        val controlPointRadiusOffset = appBarInterceptOffset - controlPointOffset

        // 从切口半径中心偏移的坐标，我们应该在此处绘制曲线
        val (curveInterceptXOffset, curveInterceptYOffset) = calculateRoundedEdgeIntercept(
            controlPointRadiusOffset,
            verticalOffset,
            cutoutRadius
        )

        // 将相对于切口圆中心的偏移量转换为绝对坐标，通过添加形状的半径来获得相对于最左边缘的纯相对偏移量，
        // 然后将其定位在切口旁边
        val curveInterceptStartX = cutoutStartPosition + (curveInterceptXOffset + cutoutRadius)
        val curveInterceptEndX = cutoutEndPosition - (curveInterceptXOffset + cutoutRadius)

        // 将相对于切口中心的curveInterceptYOffset转换为绝对位置
        val curveInterceptY = curveInterceptYOffset - verticalOffset

        // 圆形边缘开始的地方
        val roundedEdgeStartX = appBarInterceptStartX - roundedEdgeRadius
        val roundedEdgeEndX = appBarInterceptEndX + roundedEdgeRadius
/*源代码*/
//        Log.i(
//            TAG,
//            "addRoundedEdges: $roundedEdgeStartX,$roundedEdgeEndX,$appBarInterceptStartX,$appBarInterceptEndX," +
//                    "$curveInterceptStartX,$curveInterceptEndX,$curveInterceptY,$controlPointOffset,$roundedEdgeRadius"
//        )
        if (cutoutShapeTop) {
            moveTo(roundedEdgeStartX, 0f)
            quadraticBezierTo(
                appBarInterceptStartX - controlPointOffset,
                0f,
                curveInterceptStartX,
                curveInterceptY
            )
            lineTo(curveInterceptEndX, curveInterceptY)
            quadraticBezierTo(appBarInterceptEndX + controlPointOffset, 0f, roundedEdgeEndX, 0f)
        } else {
            /*自定义*/
            moveTo(roundedEdgeStartX, layoutHeight)
            quadraticBezierTo(
                appBarInterceptStartX - controlPointOffset,//872-1
                layoutHeight,//h
                curveInterceptStartX,//872.9884
                layoutHeight - curveInterceptY//h-12.94399
            )
            lineTo(curveInterceptEndX, layoutHeight - curveInterceptY)//1041.5116 h-12.94399
            quadraticBezierTo(//
                appBarInterceptEndX + controlPointOffset,//1042.5+1
                layoutHeight,//h
                roundedEdgeEndX,//1053.5
                layoutHeight//h
            )
        }
        close()

    }
}

/**
 * Helper to make the following equations easier to read
 */
@Suppress("NOTHING_TO_INLINE")
private inline fun square(x: Float) = x * x

/**
 * Returns the relative y intercept for a circle with the given [cutoutRadius] and [verticalOffset]
 *
 * Returns the leftmost intercept, so this will be a negative number that when added to the circle's
 * absolute origin will give the absolute position of the left intercept, where the circle meets
 * the app bar.
 *
 * Explanation:
 * First construct the equation for a circle with given radius and vertical offset:
 * x^2 + (y-verticalOffset)^2 = radius^2
 *
 * We want to find the y intercept where the cutout hits the top edge of the bottom app bar, so
 * rearrange and set y to 0:
 *
 * x^2 = radius^2 - (0-verticalOffset)^2
 *
 * We are only interested in the left most (negative x) solution as we mirror this for the right
 * edge later.
 */
@Suppress("NOTHING_TO_INLINE")
internal inline fun calculateCutoutCircleYIntercept(
    cutoutRadius: Float,
    verticalOffset: Float
): Float {
    return -sqrt(square(cutoutRadius) - square(verticalOffset))
}

// TODO: Consider extracting this into the shape package / similar, might be useful for cutouts in
// general.
/**
 * For a given control point on a quadratic bezier curve, calculates the required intercept
 * point to create a smooth curve between the rounded edges near the cutout, and the actual curve
 * that is part of the cutout.
 *
 * This returns the relative offset from the center of a circle with radius that is half the
 * height of the cutout.
 *
 * Explanation and derivation comes from the Flutter team: https://goo.gl/Ufzrqn
 *
 * @param controlPointX the horizontal offset of the control point from the center of the circle
 * @param verticalOffset the vertical offset of the top edge of the app bar from the center of the
 * circle. I.e, if this is 2f, then the top edge of the app bar is 2f below the center. If 0f, the
 * top edge of the app bar is in centered inside the circle.
 * @param radius the radius of the circle - essentially the 'depth' of the cutout
 */
@Suppress("UnnecessaryVariable")
internal fun calculateRoundedEdgeIntercept(
    controlPointX: Float,
    verticalOffset: Float,
    radius: Float
): Pair<Float, Float> {
    val a = controlPointX
    val b = verticalOffset
    val r = radius

    // expands to a2b2r2 + b4r2 - b2r4
    val discriminant = square(b) * square(r) * (square(a) + square(b) - square(r))
    val divisor = square(a) + square(b)
    // the '-b' part of the quadratic solution
    val bCoefficient = a * square(r)

    // Two solutions for the x coordinate relative to the midpoint of the circle
    val xSolutionA = (bCoefficient - sqrt(discriminant)) / divisor
    val xSolutionB = (bCoefficient + sqrt(discriminant)) / divisor

    // Get y coordinate from r2 = x2 + y2 -> y2 = r2 - x2
    val ySolutionA = sqrt(square(r) - square(xSolutionA))
    val ySolutionB = sqrt(square(r) - square(xSolutionB))

    // If the vertical offset is 0, the vertical center of the circle lines up with the top edge of
    // the bottom app bar, so both solutions are identical.
    // If the vertical offset is not 0, there are two distinct solutions: one that will meet in the
    // top half of the circle, and one that will meet in the bottom half of the circle. As the app
    // bar is always on the bottom edge of the circle, we are always interested in the bottom half
    // solution. To calculate which is which, it depends on whether the vertical offset is positive
    // or negative.
    val (xSolution, ySolution) = if (b > 0) {
        // When the offset is positive, the top edge of the app bar is below the center of the
        // circle. The largest solution will be the one closest to the bottom of the circle, so we
        // pick that.
        if (ySolutionA > ySolutionB) xSolutionA to ySolutionA else xSolutionB to ySolutionB
    } else {
        // When the offset is negative, the top edge of the app bar is above the center of the
        // circle. The smallest solution will be the one closest to the top of the circle, so we
        // pick that.
        if (ySolutionA < ySolutionB) xSolutionA to ySolutionA else xSolutionB to ySolutionB
    }

    // If the calculated x coordinate is further away from the origin than the control point, the
    // curve will fold back on itself. In this scenario, we actually join the circle above the
    // center, so invert the y coordinate.
    val adjustedYSolution = if (xSolution < controlPointX) -ySolution else ySolution
    return xSolution to adjustedYSolution
}


private val AppBarHeight = 56.dp

// TODO: this should probably be part of the touch target of the start and end icons, clarify this
private val AppBarHorizontalPadding = 4.dp

// Start inset for the title when there is no navigation icon provided
private val TitleInsetWithoutIcon = Modifier.width(16.dp - AppBarHorizontalPadding)

// Start inset for the title when there is a navigation icon provided
private val TitleIconModifier = Modifier
    .fillMaxHeight()
    .width(72.dp - AppBarHorizontalPadding)

// FAB 和切口之间的所有侧面的间隙
private val BottomAppBarCutoutOffset = 5.dp

// 圆边距缺口多远
private val BottomAppBarRoundedEdgeRadius = 4.dp
